Repository navigation
perf(spec): a bundle that never reads the ADR-0087 conversion table stops keeping it, 226 KB gzip off the console first screen (#22044) - #22048
Conversation
…a bundle of ./shared stops keeping every conversion Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
… conversion table only when it reads it Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
… that do not read it Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2de71f32cd64d6d1f7f97567ddc15f0f989699bb && git checkout 2de71f32cd64d6d1f7f97567ddc15f0f989699bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8caa131e52717ef35fe71742d758f10b8f2b77ee cf854f71dc379b62288390cd779476586644db3b && git checkout -B drift-repro 8caa131e52717ef35fe71742d758f10b8f2b77ee && git merge --no-ff cf854f71dc379b62288390cd779476586644db3b
node scripts/docs-audit/affected-docs.mjs --json 8caa131e52717ef35fe71742d758f10b8f2b77ee |
Contract reviewServed-tier: Inputs read: card #22044 (body; triage ① Derived judgmentsAccept set: none changes. Right. The only Public surface, what reaches a published file. Right, with one precision note. Where each annotation sits, and which call it covers. Right.
Are the two calls free of side effects? Yes. The pin,
Check-runs on the head, as read once, not polled: 32 in all. 14 ② Semver level
③ Boundary flagsDev report deviations, each answered:
Reviewer's own flags:
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ames objectui value-imports from ./shared Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
…pens (objectstack-ai#22092) Fixes objectstack-ai#22076 Clause-②: no ## What changes The `pm_dispatch_gates` arm of `scripts/ci/select-gate-families.sh` decides the `if:` of the `PM dispatch-gates self-test` step in `Lint & Repo Gates` on `pull_request` and `merge_group`. It now keys on the files the battery actually **opens**, measured, instead of on whole directories. Three classes stop selecting the step: 1. **Data and prose under `scripts/` outside `scripts/pm/`** (`.json`, `.md`, `.txt`: ratchet baselines, pinned ledgers, fixture logs). Exception: a file whose basename some file spells as a quoted relative specifier (`./…` or `../…`) still runs it. That is the one edge by which the discovery could open such a file: it follows imports out of gate sources. 2. **Agent prose and JSON outside the pm-dispatch rulebook**: `skills/**`, `AGENTS.md`, `CLAUDE.md`, `.claude/agents/**`, `.claude/settings.json`, and the other skills under `.claude/skills/`. 3. **A test under a workspace package's `scripts/`** whose basename no `package.json` and nothing under `.github/` spells. Unchanged: - `push` to `main` and the scheduled run take the selector's unscoped `*)` event branch and run the whole battery (`select-gate-families.sh:27`–`:28`). - `Lint & Repo Gates` stays a required context. - The battery keeps one tier. - There is no edit to `scripts/pm/dispatch-gates.mjs`, `scripts/pm/check-dispatch-gates.mjs` or `.github/workflows/lint.yml`. - The other nine families are untouched. Both new guards fail open: any `git grep` answer other than "no match" runs the family. Every extension the arm does not list also runs it. ## Read-set: before → after (the `pm_dispatch_gates` arm) | change touches | before | after | |---|---|---| | `scripts/pm/**` (any file) | run | run | | a code file under the root `scripts/` | run | run | | `.json` / `.md` / `.txt` under `scripts/`, outside `scripts/pm/` | run | **skip**, unless a relative specifier names it | | a non-test file under a package's `scripts/` | run | run | | a test under a package's `scripts/` | run | **skip**, unless a manifest or `.github/` names it | | any `package.json` | run | run | | `.github/**` | run | run | | `.claude/skills/pm-dispatch/**` | run | run | | `.claude/**` code (hook `.sh`, workflow `.js`) | run | run | | `skills/**`, `AGENTS.md`, `CLAUDE.md`, other `.claude/**` `.md`/`.json` | run | **skip** | | product code, docs, changesets | skip | skip | | root config, unknown path, deletion/rename, empty diff | every family | every family | ## How the read-set was measured I ran the full battery once (`node scripts/pm/dispatch-gates.mjs --self-test`, tree `3d9188502e`, a detached worktree) under a preload hook. The hook was installed through `NODE_OPTIONS=--import`, so every node child the battery spawns carried it too. It recorded: - every `readFileSync` / `readdirSync` / `existsSync` / `statSync` / `openSync` under the repo root, with the innermost `selfTest()` line that caused it; - every child process with its argv and cwd. Result: 1,976 cases pass, exit 0. There were 66 node processes, 8,430 distinct files opened under the repo root, and 57 `git ls-files` calls at the root. What the battery opens, by reader: | reader | files opened | |---|---| | compound-anchor census (`dispatch-gates.mjs:18406`) and exposed-scratch-dir sweep (`:26643`), whole tree | every masked source, 8,319 (`.ts .tsx .mts .mjs .js`) | | error-code literal census (`:20834`) | every non-test TypeScript file, 3,208 | | shell-mask census (`:19045`) | every tracked `.sh`, 30 (10 of them `.claude/hooks/*.sh`) | | discovery and live cases | all 39 workflows and 2 composite actions; 281 code files under `scripts/` and 40 under `scripts/pm/`; 31 under `packages/{spec,lint,platform-objects,services/service-messaging}/scripts/` (29 gate sources, 2 `i18n-extract.config.ts`, no test); root `package.json`, `packages/{client,lint,spec}/package.json`, root `tsconfig.json`, root `.gitignore` | | rulebook live cases (`:26122`–`:26290`) | 32 `.md` under `.claude/skills/pm-dispatch/` | | live specimens | 10 named product files (e.g. `packages/objectql/src/engine.ts`, `packages/rest/src/rest-server.ts`) | Never opened: any `.json` beyond those four manifests and `tsconfig.json`, and any `.md` outside the rulebook. The same holds for: - any `.txt`; - any `.yml` outside workflows and actions; - anything in `skills/**`, `AGENTS.md` and `CLAUDE.md`; - `.claude/agents/**`, `.claude/settings.json`, `.claude/launch.json` and the other `.claude/skills/*`; - 136 of the 167 files under package `scripts/` dirs. Name-level reads only: - `existsSync` on `.claude/agents/os-dev.md`, `skills/` and three `skills/*/SKILL.md`. These are the governed read floor and the frame-sync `COPIES` table, which the battery reads by importing `scripts/check-skill-frame-sync.mjs`. - The `.github/workflows/` listing. - The tracked name set. Why the narrowing does not depend on this one tree: - `resolveCheckToFiles` admits only `scripts/…\.(mjs|cjs|js|sh|ts|mts|cts)` as a gate source. - `firstPartyImportBindings` follows only `./`/`../` specifiers that resolve under the root `scripts/`, and never into a package's `scripts/`. - The rulebook is read by a hard-coded root (`SKILL_RULEBOOK_ROOT`). So the only tree-dependent ways into the three classes are a relative import, and a manifest or workflow naming a package-scripts test. Both are guarded. ## Which input classes can move a verdict, and how each was measured | input class | battery reader | can a modification move a verdict? | measured by | |---|---|---|---| | `scripts/pm/**`, root `scripts/` code, package `scripts/` gate sources, workflows/actions, the four manifests, rulebook | discovery and live cases | yes | read hook (content opens attributed to non-census `selfTest()` lines) | | `.claude/**` and `packages/**` code, `.sh` anywhere | whole-tree censuses | yes, census-grade | read hook (census lines `:18406`, `:26643`, `:20834`, `:19045`) | | 10 named product files | live specimens | yes, already off the PR path since objectstack-ai#19498 | read hook | | `scripts/` `.json`/`.md`/`.txt`, agent prose/JSON | nobody; existence only for 5 names | **no** | read hook (zero opens); `resolveCheckToFiles` / import-follow code reading | | package `scripts/` tests, unwired | the two whole-tree censuses only | census-grade only, as for a `src/` test | read hook; `git grep` of every manifest and `.github/` for each of the 67 such tests (2 are named: `root-entry-type-nameability.pin.test.ts` is a `lint.yml` step, and `export-list.test.ts` is mentioned in a `ci.yml` comment) | | tracked names | hint reachability, test-file residue | an **addition** can | `git ls-files` spawns in the hook log | ## Coverage that moves from PR time to `push` / scheduled runs - **For the two ratchet-grade classes** (scripts data/prose, agent prose/JSON): only the **name half** moves. - An added file there is seen by the tracked-name sweep, and is now judged by its class, as every other class has been since objectstack-ai#19498. - No content coverage moves: no case reads these bytes. - A deletion or rename still runs every family. - **For unwired package-scripts tests:** the compound-anchor census and the exposed-scratch-dir sweep over those files. This is the same census coverage objectstack-ai#19498 already moved for tests under `src/`. - **Nothing else moves.** A data file a gate imports, and a test a manifest or workflow wires, both still run the step at PR time. ## Effect, replayed I replayed the real selector, before and after, over the last 300 first-parent merges on `main` ending at `3d9188502e`. Each merge ran as a `merge_group` entry with base = first parent, in a no-checkout worktree. - The family is selected on **95 → 55** merges (31.7% → 18.3%). Forty flip `run → skip` and none flip the other way. - Of those 40: - 32 were selected only by a scripts data file outside `scripts/pm/`. 25 of them are `scripts/engine-double-contract.pinned.json`, plus `doc-authoring-prose-id.baseline.json`, `test-shard-timings.json` and others. - 7 touch agent prose: `skills/**`. - 1 touches a package-scripts test. That one is `93125aeeb8`, PR objectstack-ai#22048, the queue entry the card measured at 10.2 min for the step. It touched `packages/spec/scripts/conversions-major18-merge.test.ts` and `pure-schema-construction.test.ts`. - 3 merges stay `run` because the specifier guard's superset grep hit: - `eslint.config.mjs` reads `./scripts/query-options-erasure-baseline.json` through `new URL`; - a comment in `scripts/docs-audit/README.md` names `handwritten-docs.json`. ## Pins (selector self-test case names) Triage pin 1, "a PR touching only product code skips the family": - `pull_request: a PR touching only packages/*/src/** runs no PM self-test (objectstack-ai#22076 pin)` (new; the card's own pin) - `pull_request: a spec source, an unwired test under packages/spec/scripts and a changeset run no PM self-test (objectstack-ai#22076)` (new; the objectstack-ai#22048 shape) - `pull_request: the PR objectstack-ai#19314 shape pays the two ratchets and the corpus, and no tooling self-test (the measurement this narrowing was ruled from)` (kept) Triage pin 2, "a PR touching a gate's source, a workflow's gate wiring or `scripts/pm/**` runs it": - `pull_request: a PR touching scripts/pm/** runs the PM self-test, prose included (objectstack-ai#22076 pin)` (new) - `merge_group: a data file under scripts/pm still runs the PM self-test -- scripts/pm is in its read-set whole` (new) - `merge_group: a test under a package scripts/ that a check script names is a gate source and still runs the PM self-test` (new) - `merge_group: a data file a relative import specifier names still runs the PM self-test -- the one edge by which the discovery opens a data file` (new) - `merge_group: a workflow change runs every family built on the dispatch derivation, and not the scripts-only sweep` (kept) - `merge_group: a composite action is part of the workflow tree the derivation discovers` (kept) - `merge_group: a scripts/ subdirectory script is a gate source only` (kept) - `merge_group: a package-local script is a gate source (the derivation families) and a masked source (corpus)` (kept) Triage pin 3, "the scheduled run still runs it (control)": - `schedule: a change the PR path skips the PM self-test for still runs it -- the hourly full run is the backstop (objectstack-ai#22076 control)` (new) - `push: the same change on main still runs the PM self-test (objectstack-ai#22076 control)` (new) - Non-vacuity partner: `merge_group: the same change, scoped, skips the PM self-test -- so the two controls above are not vacuous` (new) Other new or re-pinned cases: - `a ratchet baseline … not the PM self-test`, re-pinned. It previously expected the PM self-test to run. - `agent prose the battery never opens … runs no family`, re-pinned. It previously expected the PM self-test alone. - `prose and a fixture log under scripts/ …` - `CLAUDE.md and the .claude settings JSON run no family` - `the pm-dispatch rulebook runs the PM self-test alone` - `a .claude hook shell script still runs the PM self-test alone` - `a scripts/ file of a kind the arm does not list still runs the PM self-test -- fail-open on the extension` - `a test under a package scripts/ that no manifest or workflow names … skips` The floor goes from 56 cases / 293 checks to **71 cases / 369 checks**. ## Reverse verification (on committed `1ed9df2285`) - **Leg A:** the selector restored to `3d9188502e` (`git restore --source`; on-disk blob `e6c2f3c2` == base blob), run under the new self-test. Exit 1, with 7 cases red and 20 checks failing — every narrowing case, plus the non-vacuity partner. Restore: blob `76f368b7` == HEAD, and `git diff HEAD` is empty. - **Leg B:** `node scripts/ablation-replace.mjs --delete` on the `named_by_relative_specifier` call (anchor 1 → 0). Exit 1; exactly `a data file a relative import specifier names still runs the PM self-test` goes red (3 checks). Restore proven: blob == HEAD. - **Leg C:** the same on the `named_in_gate_wiring` call. Exit 1; exactly `a test under a package scripts/ that a check script names … still runs the PM self-test` goes red (3 checks). Restore proven. ## Gates (head `1ed9df2285`) I derived the gate list with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 27 commands. I ran each one and wrote its exit code to disk before reading it. All 26 non-battery commands exit 0. They include: - `pnpm check:select-gate-families` (71 cases / 369 checks) - `check:bash32-floor`, `check:parse-guard`, `check:nul-bytes` - `check-ci-filter-parity`, `check-self-test-workflow-commands` (with and without `--self-test`), `check-comment-mask-corpus`, `check:entry-guard` `pnpm check:pm-dispatch-gates` (the control) ran detached on `1ed9df2285` (not under the verify lock), on a shared 4-core container at load 5–8. Result: **exit 0**, `✓ dispatch-gates self-test: 1976 cases pass.`, `the battery took 1004.0s on this box`, wall 1,006 s (2026-10-07T14:39:27Z → 14:56:13Z). That is a reading of a contended agent box, not of a runner. The instrumented measurement run at `3d9188502e` also passed all 1,976 cases. The record is reconciled with `--ran`: `✓ dispatch-gates --ran: 27 derived famil(ies) accounted for — 27 run, 0 NOT-MEASURED`. ## Premise checks 1. **"Selects the step on most PRs."** By rule, any `package.json` and anything under any `scripts/` selected it. Measured over 300 merges, it selected 95, which is 31.7%, not most. The scripts data files carried 32 of those 95 on their own. 2. **"The censuses are deliberately off the PR path."** In code this means the selector returns `skip` for product code, so those inputs never select the family. It does **not** mean the censuses are skipped once the family is selected: the battery has one tier, and every selected run pays all of them. The hook found four whole-tree censuses, not the two the old comment named. The two it did not name are the error-code literal census over 3,208 files and the `.sh` census. It also found 10 live product specimens. The header now names all of them. 3. **"A `package.json` change that does not touch a `check:*` script may not move the verdict."** It may not, but the narrowing is not taken. In the replay, 13 merges touched a workspace manifest: - 10 also touched `pnpm-lock.yaml` (root config, so every family runs anyway); - 2 are structural (a deletion runs every family anyway); - 1 alone would gain. The set of manifests the discovery reads is also tree-dependent: on this tree it is root, `client`, `lint` and `spec`, decided by the `--filter` rows workflows spell. A `scripts`/`name` field diff would need `node` in the selector and a parse fail-open branch to save about 1 run in 300. ## Notes - **File surface.** The selector has no `--self-test` flag. Its self-test is the sibling `scripts/ci/select-gate-families.selftest.sh` (`pnpm check:select-gate-families`), which is where the pins live, so the diff is those two files. - **Not taken: the card's "two tiers".** Triage did not take it: it edits the frozen `dispatch-gates.mjs`. - **NOT MEASURED: the card's "wall under 18 minutes" and "the merge_group run's critical path with run ids".** Reason: they can be read only from CI runs after this lands, on a product-only PR and on a queue entry. - **Not narrowed: `.github/` files outside workflows and actions** (`CODEOWNERS`, `labeler.yml`, `dependabot.yml`). The battery never opens them, but none of the 300 merges touched them, so the arm would buy nothing. - **Not narrowed: the three other derivation families.** `declared_population_live`, `bare_root_worklist` and `self_test_workflow_commands` share `reads_gate_tree` and still run on scripts data and on package-scripts tests. Whether they can narrow too is not measured here. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22044
Clause-②: no
This is the source-side payback for the maintainer's ruling on objectui#11717. The record is comment
6027998226, and the selection was 「A 抬预算+回收卡 (Recommended)」. objectui raised its console first-screen budget to let 17.7.0 in (objectui#11742), and this card brings it back down.Measured on objectui's console at objectui
c0862c1c, which resolves 17.7.0: the first screen's eager closure is 226,238 bytes gzip smaller, and all of the change is in thevendor-objectstackchunk. The 17.7.0 bump added +182,909 bytes gzip to that chunk. No entry's export list changes, and no declaration or runtime value changes. Only the bytes a bundler keeps change.What grew between the tags, module by module
Instrument: source-map attribution of each published ESM entry file. The tarball ships an
.mjs.mapfor each entry, and each generated byte is assigned to the source module of the mapping it falls under. The comparison is npm@objectstack/spec@17.6.0against@objectstack/spec@17.7.0. All figures are raw bytes.dist/shared/index.mjs: 980,505 to 1,503,348 bytes (+522,843), with the same 79 exports../sharedsrc/ui/component.zod.tssrc/ui/view.zod.tssrc/data/field.zod.tssrc/ui/dashboard.zod.tssrc/conversions/registry.tssrc/ui/chart.zod.tssrc/ui/report.zod.tssrc/ui/bulk-action.zod.tsdist/browser/index.mjs(the root a browser bundler loads): 2,348,750 to 2,593,955 bytes (+245,205).src/ui/component.zod.tssrc/conversions/registry.tssrc/automation/approval.zod.tssrc/ai/agent.zod.tssrc/system/job.zod.tssrc/ui/chart.zod.tssrc/ui/page.zod.tsHow
./sharedreaches those modules. This is the esbuild metafile of the 17.7.0 source, shortest path.shared/index.tsimportsshared/metadata-collection.zod.ts, which importsconversions/apply.ts(fornormalizeStackInput). That importsconversions/registry.ts, and from there the schemas the conversions read:ui/view.zod.ts, thenchart.zod.tsandbulk-action.zod.ts;conversions/walk.ts:ui/component.zod.ts, thendashboard.zod.tsandreport.zod.ts.data/field.zod.tsis also reached throughshared/field-type-suggestion.ts. The shared source itself grew very little: its own modules are under 10 KB of the delta.Why the console kept it
Every top-level call stays. Each published entry is one flat file (
tsup.config.ts,splitting: false). A consumer's bundler therefore keeps every top-level call it cannot prove pure, together with everything that call references.Two calls build the conversion table at load:
18: inApplicationOrder(MAJOR_18_CONVERSIONS);Object.keys(CONVERSIONS_BY_MAJOR)chain that flattens intoALL_CONVERSIONS.Unmarked, they kept every conversion, and every schema a conversion reads, in every bundle of every entry whose graph reaches the table. They did so whether or not the bundle reads the table.
The console never reads the table, and it carried two copies. In the control's
vendor-objectstackchunk, each of the 129 conversion ids appears as anid:property exactly 2 times: the root's copy and./shared's copy. In the candidate's chunk the count is 0. That is why the recovery is larger than./shared's growth alone. The table was already in both copies at 17.6.0, and 17.7.0's new conversions made each copy larger.Consumer probe. This probe isolates
./sharedfrom the rest of the chunk. It is not the acceptance instrument. Setup:c0862c1cvalue-imports from that entry;./shared./ui./system(browser)./sharedat this head,src/conversions/registry.tskeeps 0 bytes, down from 201,720 at 17.7.0.defineStack/composeStacks, which@object-ui/typesand@object-ui/corere-export, but nothing the console bundles calls either of them. The console reading above shows the root's copy gone as well.What changed
packages/spec/src/conversions/registry.ts: both load-time calls carry a@__PURE__annotation. TheCONVERSIONS_BY_MAJORdocblock says why, says that the next major'sinApplicationOrder(…)takes the same mark, and names the pin. No conversion, no order and no value changes.Why source annotations, not the
pure-schema-constructionbuild plugin. The plugin marks calls by the name of the callee, everywhere undersrc/. TheALL_CONVERSIONSinitializer is amap/sort/flatMapchain onObject.keys, and marking those names would mark every such call in the package. Two explicit marks on the two calls are the narrowest change.tsup.config.tsis untouched, so the chunk layout and the published file set are unchanged (below).Pin: a new case in
packages/spec/scripts/pure-schema-construction.test.ts. It builds./sharedthrough tsup with the package's own main options, then builds two consumers of that output:./sharedkeep none of the 129 conversion ids;normalizeStackInput, the control, keeps all of them, which proves the instrument sees the table whenever a bundle keeps it.This is a test case, not a gate and not a byte ceiling.
packages/spec/scripts/conversions-major18-merge.test.ts: itsWIRINGstring, a textual pin of the major-18 wiring line, now spells the annotation..changeset/22044-shared-entry-drops-conversion-table.md:@objectstack/specpatch. The published bytes change and the contract does not.Console reading: the acceptance instrument
objectui
c0862c1cin a detached worktree, built the way theBundle Analysisjob builds it:Each reading comes from
apps/console/dist/eager-closure.json, which reports gzip bytes.vendor-objectstackgzipvendor-objectstackrawOBJECTSTACK_SPEC_DISTset tomain9a0401fd, builtOBJECTSTACK_SPEC_DISTset to this headeb2152b86b, builtvendor-objectstack. The other 330 eager chunks moved by +63 gzip net (import-hash churn), and the eager chunk count is 331 in all three builds.OBJECTSTACK_SPEC_DIST: it is objectui's own injection, the one the Console Pin Gate uses. Reading (b) shows it moves the control by only +248 gzip, which ismain's drift since the tag plus the injection path.Export parity
The guard is that the export surface does not change. Every comparison below sets
main9a0401fd(built in its own worktree) against this head (built):pnpm --filter @objectstack/spec check:api-surface: exit 0, "public API surface + factory signatures unchanged ✓".Object.keysof every.mjsand.jsfile the exports map names, browser arms included. 48 entry files and 7,864 names, 0 differing. Control leg: npm 17.6.0 against this head reads 16 differing (exit 1).tsup.config.tsDTS docblock prescribes (sort union members, property-only type-literal members, statements and specifiers; strip chunk hashes; pair chunks by content). 130 files, 38 entries and 62 chunk contents on each side: identical. Control leg: npm 17.6.0 against 17.7.0 reads 52 differences (exit 1).dist/holds 228 files on both sides, with chunk hashes stripped, and the lists are identical. The chunk layout does not move, sopublish-smokeis not owed andneeds:pack-smokeis not needed.dist/shared/index.mjsgoes from 1,510,391 to 1,510,423 bytes.Verification record
The final head is
eb2152b86b. Its last commit adds only the changeset, so readings taken at08392e53a4cover the samesrc/andscripts/tree.Reverse verification of the pin. Both annotations were committed first, then each was removed in turn through
scripts/ablation-replace.mjs, at08392e53a4:ALL_CONVERSIONSmark ("mutation landed", anchor count 1 to 0) turned the pin red, with 129 ids kept against the expected[]. Restore: "blob == HEAD (d707d83fdbad) andgit diff HEADis empty".inApplicationOrdermark turned it red with the 62 major-18 ids kept. Restored the same way.src/through tsup, so nodist/sits between the mutation and the assertion.Tests:
@objectstack/speclocal project, full (pnpm --filter @objectstack/spec test)eb2152b86bscripts/pure-schema-construction.test.ts08392e53a4scripts/conversions-major18-merge.test.ts, repo project08392e53a4The rest of the
repoproject is declared to CI.Typecheck:
pnpm --filter @objectstack/spec typecheck(tsc, scripts and test layer) passed ateb2152b86b.Build:
pnpm --filter @objectstack/spec build(the JS and DTS passes,check-dts-emitted,check-dts-references) exited 0 atd017de4582and ateb2152b86b.Gates, at
eb2152b86b:node scripts/pm/dispatch-gates.mjs --commandsderives 86 families.--ranreconciles them: 86 run, 0 NOT-MEASURED, 0 UNRUN. Every recorded exit code is 0.pnpm turbo run build:check:dual-build-cjs-loads,check:lean-entry-closureand@objectstack/lintcheck:doc-formula-expressions.pnpm --filter @objectstack/spec check:generated("All 15 generated artifacts are up to date").check:authorable-surface,check:yaml-examples, thecheck-governed-queue-guardself-test,check:console-injectionandcheck:console-sha. The last two skipped for want ofpackages/console/dist, so they are NOT MEASURED here.Lint, narrowed and proven, at
eb2152b86b:eslint --no-inline-config --format jsonover the 3 changed.tsfiles, which is every changed file in eslint's population (**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}). The changeset is outside it.eslint.config.mjsenables no type-aware linting (noparserOptions.project, no typed rules), so this diff cannot move any untouched file's verdict. The full-treepnpm lintis CI's.Acceptance notes
BASELINEby the recovered amount is objectui's follow-up, once it resolves a release that carries this change. The reading to lower by is the (b) − (c) line above. No objectui commit, branch or PR was made: the objectui worktree was used for measurement only, and was removed../sharedafter this change (consumer probe: 144,609 raw / 47,491 gzip for the console's 5 names). Most of it is argument expressions of calls the build marks pure. AstrictObject({ … })call is dropped when unused, but arguments that have side effects still run and are kept, for example barez.*chains,.optional().describe(…)on an imported schema, andcomponentSlot(…).src/ui/component.zod.ts34,556,src/ui/view.zod.ts18,973,src/data/filter.zod.tsabout 18,000.src/ui/component.zod.tsis kept at about 157 to 159 KB raw in each of./ui, the root and./system, because each flat entry carries its own copy.tsup.config.tsdocblock asks for that review first.Generated by Claude Code